

//拼图操作
class Puzzle {

	constructor(fsPath){
		this.fsPath = fsPath;
	}

	//柯里化
	curry(...params){
		return this.curryList(params)
	}
	curryList(params){
		return new PuzzleCurryOperate({puzzle:this,curryParam:params}).initData()
	}

	static of(puzzleFile){
		return new Puzzle(puzzleFile)
	}
}


//基础货币
class Coin {
	constructor(baseCoin){
		this.ext = {baseCoin}
	}

	isSpent(){
		return this.ext.baseCoin.spent;
	}

	getId(){
		return SHELL(`run "(sha256 ${this.getParentCoin()} ${this.getPuzzleHash()} ${this.getAmount()} )"`)
	}

	getAmount(){
		return this.ext.baseCoin.coin.amount;
	}

	getParentCoin(){
		return this.ext.baseCoin.coin.parent_coin_info;
	}

	getPuzzleHash(){
		return this.ext.baseCoin.coin.puzzle_hash;
	}

	getBaseSpentCoinInfo(){
		const removeHex = x => x.substring(2,x.length)
		return {
			amount:this.getAmount(),
			parent_coin_info:removeHex(this.getParentCoin()),
			puzzle_hash:removeHex(this.getPuzzleHash())
		}
	}

	attr(a){
		switch(a){
			case "id":return this.getId();
			case "puzzle_hash":return this.getPuzzleHash();
			case "amount":return this.getAmount();
			default:
				throw `not set the attribute:${a}`
		}
	}

		//获取属性
	attrs(attrList){
		return attrList.map(this.attr.bind(this))
	}

	equal(coin){
		return this.getAmount() == coin.getAmount() 
		&& this.getParentCoin() == coin.getParentCoin()
		&& this.getPuzzleHash() == coin.getPuzzleHash()
	}

	print(){
		console.log(JSON.stringify(this,null,2))
	}

	setSolution(solution){
		this.ext.solutionParam = solution;
		//this.ext.solution = OPC(`(${solution.join(" ")})`);
		this.ext.solution = OPC(CONVERT_OPC(solution));
		return this;
	}


	run(){
		const {curry,solutionParam} = this.ext;
		BRUN(curry,CONVERT_OPC(solutionParam))
	}

	getJoinSpendBundlerCoin(){
		return [this]
	}


}

//拼图柯里化后操作
class PuzzleCurryOperate extends Coin{

	constructor(ext){
		super({})
		this.ext = ext;
	}

	getPuzzleHash(){
		const {treehash} = this.ext;
		return treehash.indexOf("0x") >= 0 ? treehash : "0x"+treehash;
	}

	initData(){
		const {puzzle,curryParam} = this.ext;
		const data = PUZZLE_RESOLVE(puzzle.fsPath,curryParam)
		this.ext = {...this.ext,...data}
		return this;
	}

	curry(...curryParam){
		const {curry} = this.ext;
		const data = PUZZLE_RESOLVE(`"${curry}"`,curryParam)
		this.ext = {...this.ext,...data}
		return this;
	}

	// run(solution){
	// 	const {curry} = this.ext;
	// 	BRUN(curry,`(${solution.join(" ")})`)
	// }



	getCoins(){
		return COIN_RECORDS_HASH(this.ext.treehash).map(baseCoin => new DeployCoin(this.ext,baseCoin))
	}

	getUnSpentCoins(amount){
		const coinList = this.getCoins().filter(dc => !dc.isSpent())
		return amount != undefined ? coinList.filter(s => s.getAmount() == amount) : coinList;
	}

	getSpentCoins(amount){
		const coinList = this.getCoins().filter(dc => dc.isSpent())
		return amount != undefined ? coinList.filter(s => s.getAmount() == amount) : coinList;
	}


	deploy(fingerprint,amount){

		const {address} = this.ext;

		const depolyResult = DEPLOY_PUZZLE(fingerprint,amount,address)

		ASSERT_HAS(depolyResult,"Do chia wallet get_transaction")

		const transactionId = depolyResult.match(/\s0x\w+\s/g)[0]

		return new TransactionOperate({
			...this.ext,
			puzzleCurryOperate:this,
			fingerprint,
			transactionId,
			amount
		})

	}

}

//部署成功操作
class TransactionOperate {

	constructor(ext){
		this.ext = ext
	}

	waitConfirmCall(delay=10,cb){
		const {fingerprint,transactionId,amount} = this.ext;
		const flag = setInterval(()=>{

			const result = TRANSACTION_STATE(fingerprint,transactionId,false)

			console.log(result)

			if(result.indexOf("Status: Confirmed") > -1){
				clearInterval(flag)
				cb(this)
			}

		},delay*1000)
	}

	waitConfirm(delay){
		return new Promise((resolve)=> this.waitConfirmCall(delay,resolve))
	}

	//当交易成功后，获得一个未花费且数量相等的货币
	async getCoin(){
		const {puzzleCurryOperate,amount} = this.ext
		await this.waitConfirm()
		const coins = puzzleCurryOperate.getUnSpentCoins(amount)
		coins.length == 0 && ERROR("no coin found!")
		return coins[0]
	}

}



//部署的货币
class DeployCoin extends Coin {

	constructor(ext,baseCoin){
		super(baseCoin)
		//this.ext 必须在后面，这样baseCoin的属性可以覆盖原先的
		this.ext = {...ext,...this.ext}
	}



	setSignature(params,privateKey){
		this.ext.signature = {
			privateKey,
			params
		}
	}

	setSolutionWithSignature(solution,privateKey){
		this.setSolution(solution)
		this.setSignature(solution,privateKey)
	}


	setSolution(solution,sk){
		if(sk){
			this.setSolutionWithSignature(solution,sk);
		}else{
			super.setSolution(solution)
		}
		return this;
	}

	getSpentInfo(){

		const {puzzle_reveal,solution} = this.ext;

		return {
			coin:this.getBaseSpentCoinInfo(),
			puzzle_reveal,
			solution
		}
	}


	doSpent(){
		return Rpc.of([this]).spent()
	}

	//获取已经花费的货币
	getSelfSpentInfo(){
		return new PuzzleCurryOperate(this.ext).getSpentCoins(this.getAmount()).find(c => c.equal(this))
	}


	isNeedSignature(){
		return this.ext.signature;
	}


	bindSignature(){
		return PRIVATE_SIGNATURE(this.ext.signature)
	}



}

class StandardCoin extends DeployCoin {
	constructor(deployCoin){
		super(deployCoin.ext,deployCoin.ext.baseCoin)
	}

	isNeedSignature(){
		return true;
	}

	addCondition(conditionList,sk){
		this.conditionList = conditionList;
		this.sk = sk;
		return this;
	}

	setSolution(solution,sk){
		if(!sk)
			throw 'the secretkey must be exist when StandardCoin set solution'
		return this.addCondition(solution,sk)
	}

	bindSignature(){

		const {conditionList,sk} = this;

		const signData = {
			sk,
			puzzleHash:this.getPuzzleHash(),
			id:this.getId(),
			args:conditionList
		}

		const signBackData = STANDER_COIN_SIGNATURE(signData)

		const {puzzle_reveal,solution,signature} = signBackData;

		this.ext = {...this.ext,puzzle_reveal,solution};

		return signature;

	}

	run(){

	}
}

class WrapStandardCoin {
	constructor(coins,amount,fee){
		coins.forEach(c => {
			if(! c instanceof StandardCoin){
				throw `WrapStandardCoin constructor exception:this coin must be exrend StandardCoin`
			}
		})
		
		this.amount = amount;
		this.fee = fee;
		this.initCoin(coins)
	}
	initCoin(coins){
		const [firstCoin,...lastCoin] = coins;
		this.coins = coins;
		this.firstCoin = firstCoin;
		this.lastCoin=lastCoin;
	}
	getAmount(){
		return this.amount;
	}
	getPuzzleHash(){
		return this.coins[0].getPuzzleHash()
	}
	getRealAmount(){
		return this.coins.map(v => v.getAmount()).sum()
	}
	async combine(sk){

		const {firstCoin,lastCoin} = this;


		const totalAmount = this.getRealAmount();

		const coinId = firstCoin.getId();

		const puzzleHash = this.getPuzzleHash();


		firstCoin.setSolution([
			[OPC_CODE.CREATE_COIN,puzzleHash,totalAmount],
			[OPC_CODE.CREATE_COIN_ANNOUNCEMENT,totalAmount]
		],sk)

		lastCoin.forEach(coin => {
			coin.setSolution([
				[OPC_CODE.ASSERT_COIN_ANNOUNCEMENT,SHA256(`${coinId} ${totalAmount}`)]
			],sk)
		})

		await this.doSpent().print()

		let createCoin = new PuzzleCurryOperate({treehash:puzzleHash}).getUnSpentCoins(totalAmount);

		this.initCoin(createCoin[0])

	}
	async setSolution(conditionList,sk,setCombine=true){

		const {firstCoin,lastCoin} = this;

		if(lastCoin.length > 0 && setCombine){
			await this.combine(sk)
		}

		firstCoin.setSolution([...conditionList],sk);

		const lastAmount = this.getRealAmount()-this.amount-this.fee
		if(lastAmount > 0){
			firstCoin.conditionList.push([51,firstCoin.getPuzzleHash(),lastAmount])
		}

		//very bad code.the bad node will steal your money from this code
		lastCoin.forEach(coin => {
			coin.setSolution([],sk)
		})

		return this;
	}
	doSpent(){
		return Rpc.of([this]).spent();
	}
	run(){

	}
	getJoinSpendBundlerCoin(){
		return this.coins;
	}
	print(){
		this.coins.forEach(c => c.print())
	}
}



class Rpc {


	no_aggregated_signature="c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

	constructor(coins){
		this.deployCoins = coins.reduce((s,v)=> [...s,...v.getJoinSpendBundlerCoin()] ,[])
	}


	getSignature(){
		const signatureList = this.deployCoins.filter(v => v.isNeedSignature()).map(v => v.bindSignature())

		if(signatureList.length == 0)
			return this.no_aggregated_signature;

		if(signatureList.length == 1)
			return signatureList[0];

		return AGGREGATE_SIGNATURE_LIST(signatureList);
	}

	spent(){

		const {deployCoins,no_aggregated_signature} = this;

		//test the solution or do something
		deployCoins.forEach(coin => coin.run())

		const spentJson = {
			aggregated_signature: this.getSignature(),
			coin_spends:deployCoins.map(c => c.getSpentInfo())
		}

		INFO(JSON.stringify(spentJson,null,2))

		const {realName} = CREATE_TEMP_FILE("spent.json",JSON.stringify(spentJson,null,2))

		const rpcMsg = PUSH_TX(realName)

		INFO(rpcMsg)

		try{

			const spentMsg = JSON.parse(rpcMsg)

			if(spentMsg.success){
				spentMsg.getSpendCoins = ()=> this.getAlreadySpentCoinList()

				spentMsg.print = async ()=>{
					ASSERT("spent success",spentMsg.success)
					const spendCoins = await spentMsg.getSpendCoins()
					spendCoins.forEach(c => c.print())			
				}
			}

			return spentMsg;

		}catch(e){
			return {success:false,error:rpcMsg}
		}
	}

	//获取已经消费的货币
	getAlreadySpentCoinList(){
		const {deployCoins} = this;
		return new Promise( (resolve,reject) => {

			const flag = setInterval(()=>{

				const spentInfo = deployCoins[0].getSelfSpentInfo()

				if(spentInfo){

					const lastSpent = deployCoins.slice(1,deployCoins.length).map(c => c.getSelfSpentInfo())

					clearInterval(flag)

					resolve([spentInfo,...lastSpent])

				}

			},30*1000)

		})
	}

	static of(coins){
		return new Rpc(coins)
	}

}







